package game

import (
	"base"
	"center_server/logic/center/api"
	"center_server/logic/game/api"
	"center_server/logic/gate/api"
	"center_server/logic/gate/event"
	. "center_server/logic/internal"
	"center_server/logic/player/api"
	"center_server/model"
	"emma/errors"
	"leaf/gate"
	"proto/base"
	"proto/server/game"
	"proto/server/gate"
	"proto/server/login"
	"sort"
	"time"
)

type gameSystem struct {
	servers map[base.AgentId]*gameServer
}

var system = new(gameSystem)

func init() {
	system.servers = make(map[base.AgentId]*gameServer)
}

func init() {
	game_api.Login = system.Login
}

func init() {
	Event.Listen(gate_event.OnRegister, system.onGateRegister)
	Event.Listen(gate_event.OnClose, system.onGateClose)
}

func init() {
	Skeleton.AfterInit(func() {
		center_api.RegisterHandler(proto_base.Server_GameServer, system)
	})
}

func (*gameSystem) Get(gameId base.AgentId) (result *gameServer, exists bool) {
	result, exists = system.servers[gameId]
	return
}

func (*gameSystem) OnRegister(id base.AgentId, agent gate.Agent, data []byte) (result interface{}) {
	server := new(gameServer)
	server.Agent = agent
	server.id = id
	server.players = make(map[base.PlayerId]*model.Player)
	system.servers[id] = server

	agent.SetUserData(server)
	result = gate_api.InnerAddrs()

	return
}

func (*gameSystem) OnClose(id base.AgentId, agent gate.Agent) {
	server, exists := system.servers[id]
	if !exists {
		return
	}

	for _, player := range server.players {
		if client := player.Client(); client != nil {
			gate_api.Logout(client.GateId(), client.Id(), proto_base.LogoutReason_Stop)
		}
	}

	agent.SetUserData(nil)
	delete(system.servers, id)
}

func (*gameSystem) Login(client *model.Client, msg *proto_server_login.OnLogin) (err error) {
	server := system.getServer()
	if server == nil {
		code := int(proto_base.ErrorCode_NotGameServer)
		err = errors.New(code, "没有可用的游戏服务器")
		return
	}

	playerId := base.PlayerId(msg.PlayerId)
	player, exists := player_api.Get(playerId)
	if exists {
		if other := player.Client(); other != nil && other != client {
			// 当前玩家把上个玩家挤掉线
			msg := new(proto_server_gate.OnLogout)
			msg.PlayerId = player.Id().String()
			msg.Reason = proto_base.LogoutReason_Conflict
			server.WriteMsg(msg)
		}
	} else {
		player = model.NewPlayer(playerId, server.id)
		server.players[playerId] = player
		server.playerCount++
		player_api.Add(player)
	}

	now := time.Now().Unix()
	player.SetLoginTimestamp(now)

	player.SetClient(client)
	client.SetPlayer(player)

	request := new(proto_server_game.Login)
	request.PlayerId = player.Id().String()
	request.Timestamp = player.LoginTimestamp()
	request.GateId = uint32(msg.GateId)
	server.WriteMsg(request)

	return
}

func (*gameSystem) getServer() (result *gameServer) {
	if len(system.servers) < 1 {
		return
	}

	servers := make([]*gameServer, 0, len(system.servers))
	for _, server := range system.servers {
		servers = append(servers, server)
	}

	sort.Slice(servers, func(i, j int) bool {
		return servers[i].playerCount < servers[j].playerCount
	})
	result = servers[0]

	return
}

func (*gameSystem) onLogin(server *gameServer, msg *proto_server_game.OnLogin) {
	playerId := base.PlayerId(msg.PlayerId)
	player, exists := server.players[playerId]
	if !exists {
		return
	}

	client := player.Client()
	if client == nil {
		return
	}

	code := msg.Code
	system.responseLogin(player, client, code)

	if code == proto_base.ErrorCode_Success {
		client.SetStatus(model.Client_PlayStatus)
	} else {
		delete(server.players, playerId)
		server.playerCount++

		player_api.Remove(playerId)
		gate_api.RemoveClient(client.GateId(), client.Id())
	}
}

func (*gameSystem) responseLogin(player *model.Player, client *model.Client, code proto_base.ErrorCode) {
	msg := new(proto_server_gate.OnLogin)
	msg.PlayerId = player.Id().String()
	msg.GameId = int32(player.GameId())
	msg.Code = code

	gate_api.OnLogin(client.GateId(), client.Id(), msg)
}

func (*gameSystem) onGateRegister(agentId base.AgentId, innerAddr string) {
	msg := new(proto_server_game.OnGateRegister)
	msg.AgentId = uint32(agentId)
	msg.Addr = innerAddr

	for _, item := range system.servers {
		item.WriteMsg(msg)
	}
}

func (*gameSystem) onGateClose(agentId base.AgentId, innerAddr string) {
	msg := new(proto_server_game.OnGateClose)
	msg.AgentId = uint32(agentId)

	for _, item := range system.servers {
		item.WriteMsg(msg)
	}
}
